package com.aabte.nettyleran.netty.privateprotocolstack;

import com.aabte.nettyleran.util.MarshallingCodecFactory;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
import org.jboss.marshalling.ByteOutput;
import org.jboss.marshalling.Marshaller;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

/**
 * @author Daniel
 * @version 1.0
 * @date 2020/3/15
 */
public final class NettyMessageEncoder extends MessageToMessageEncoder<NettyMessage> {

    private final MarshallingEncoder marshallingEncoder = new MarshallingEncoder();

    public NettyMessageEncoder() throws IOException {
    }

    @Override
    protected void encode(ChannelHandlerContext ctx, NettyMessage msg, List<Object> out) throws Exception {
        if (null == msg || null == msg.getHeader()) {
            throw new IllegalArgumentException("The encode message is null.");
        }

        Header header = msg.getHeader();

        ByteBuf sendBuf = Unpooled.buffer();
        sendBuf.writeInt(header.getCrcCode());
        sendBuf.writeInt(header.getLength());
        sendBuf.writeLong(header.getSessionId());
        sendBuf.writeByte(header.getType());
        sendBuf.writeByte(header.getPriority());
        sendBuf.writeInt(header.getAttachment().size());

        String key = null;
        byte[] keyArray = null;
        Object value = null;
        for (Map.Entry<String, Object> entry : header.getAttachment().entrySet()) {
            key = entry.getKey();
            keyArray = key.getBytes(StandardCharsets.UTF_8);
            sendBuf.writeInt(keyArray.length);
            sendBuf.writeBytes(keyArray);

            value = entry.getValue();
            marshallingEncoder.encode(value, sendBuf);
        }

        key = null;
        keyArray = null;
        value = null;

        if (msg.getBody() != null) {
            marshallingEncoder.encode(msg.getBody(), sendBuf);
        } else {
            sendBuf.writeInt(0);
            sendBuf.setInt(4, sendBuf.readableBytes());
        }

        out.add(sendBuf);
    }

    private static class MarshallingEncoder {

        private static final byte[] LENGTH_PLACEHOLDER = new byte[4];

        private Marshaller marshaller;

        public MarshallingEncoder() throws IOException {
            this.marshaller = MarshallingCodecFactory.buildMarshalling();
        }

        protected void encode(Object msg, ByteBuf out) throws IOException {
            try {
                int lengthPos = out.writerIndex();
                out.writeBytes(LENGTH_PLACEHOLDER);
                ByteOutput output = new ChannelBufferByteOutput(out);
                marshaller.start(output);
                marshaller.writeObject(msg);
                marshaller.finish();
                out.setInt(lengthPos, out.writerIndex() - lengthPos - 4);
            } finally {
                marshaller.close();
            }
        }
    }

}
